package com.medusa.aps.business.modules.demand.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.medusa.aps.business.model.constant.CommonConstants;
import com.medusa.aps.business.model.constant.RedisConstants;
import com.medusa.aps.business.model.enums.LabelStatus;
import com.medusa.aps.business.model.enums.ProductionType;
import com.medusa.aps.business.model.enums.SchedulingStatusEnum;
import com.medusa.aps.business.model.planingscheduing.DeviceWorkingCalendar;
import com.medusa.aps.business.model.planingscheduing.EquipmentProcess;
import com.medusa.aps.business.model.planingscheduing.MaterialSupplier;
import com.medusa.aps.business.modules.basic.mp.entity.Material;
import com.medusa.aps.business.modules.basic.mp.mapper.SupplierMapper;
import com.medusa.aps.business.modules.basic.mp.mapper.WorkingCalendarMapper;
import com.medusa.aps.business.modules.basic.mp.service.MaterialService;
import com.medusa.aps.business.modules.demand.mp.entity.GlobalSettingsForScheduling;
import com.medusa.aps.business.modules.demand.mp.entity.OrderInformation;
import com.medusa.aps.business.modules.demand.mp.entity.PlanOperationProgress;
import com.medusa.aps.business.modules.demand.mp.service.GlobalSettingsForSchedulingService;
import com.medusa.aps.business.modules.demand.mp.service.OrderInformationService;
import com.medusa.aps.business.modules.demand.service.PlanningOperationService;
import com.medusa.aps.business.modules.modeling.mp.entity.AuxiliaryData;
import com.medusa.aps.business.modules.modeling.mp.entity.Equipment;
import com.medusa.aps.business.modules.modeling.mp.entity.ProcessEquipment;
import com.medusa.aps.business.modules.modeling.mp.entity.TechnologicalProcess;
import com.medusa.aps.business.modules.modeling.mp.mapper.EquipmentMapper;
import com.medusa.aps.business.modules.modeling.mp.mapper.TechnologicalProcessMapper;
import com.medusa.aps.business.modules.modeling.mp.service.AuxiliaryDataService;
import com.medusa.aps.business.modules.modeling.mp.service.EquipmentService;
import com.medusa.aps.business.modules.modeling.mp.service.ProcessEquipmentService;
import com.medusa.aps.business.modules.plan.mp.entity.ProcessDetails;
import com.medusa.aps.business.modules.plan.mp.entity.PurchasePlan;
import com.medusa.aps.business.modules.plan.mp.entity.SchedulingTaskView;
import com.medusa.aps.business.modules.plan.mp.entity.SuggestedPlan;
import com.medusa.aps.business.modules.plan.mp.service.ProcessDetailsService;
import com.medusa.aps.business.modules.plan.mp.service.PurchasePlanService;
import com.medusa.aps.business.modules.plan.mp.service.SchedulingTaskViewService;
import com.medusa.aps.business.modules.plan.mp.service.SuggestedPlanService;
import com.medusa.aps.business.common.redis.RedisUtil;
import com.medusa.aps.business.global.model.exception.GlobalException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: xh.yu
 * @createTime: 2023/09/04 17:09
 */
@Service("planningOperationService")
@Slf4j
public class PlanningOperationServiceImpl implements PlanningOperationService {

    @Resource
    private WorkingCalendarMapper workingCalendarMapper;
    @Resource
    private EquipmentMapper equipmentMapper;
    @Resource
    private ProcessEquipmentService processEquipmentService;
    @Resource
    private SuggestedPlanService suggestedPlanService;
    @Resource
    private MaterialService materialService;
    @Resource
    private EquipmentService equipmentService;
    @Resource
    private ProcessDetailsService processDetailsService;
    @Resource
    private SchedulingTaskViewService schedulingTaskViewService;
    @Resource
    private PurchasePlanService purchasePlanService;
    @Resource
    private OrderInformationService orderInformationService;
    @Resource
    private SupplierMapper supplierMapper;
    @Resource
    private GlobalSettingsForSchedulingService globalSettingsForSchedulingService;
    @Resource
    private TechnologicalProcessMapper technologicalProcessMapper;
    @Resource
    private AuxiliaryDataService auxiliaryDataService;

    /**
     * 获取开始排程时间
     *
     * @param globalSettingsForScheduling 全局参数
     * @return 开始排程时间
     */
    private static LocalDateTime getGroubleStartTime(GlobalSettingsForScheduling globalSettingsForScheduling) {
        //查询开始排程时间
        LocalDateTime groubleStartTime = LocalDateTime.now();
        if (globalSettingsForScheduling != null && globalSettingsForScheduling.getSchedulingStartTime() != null) {
            if (globalSettingsForScheduling.getSchedulingStartTime().isAfter(groubleStartTime.toLocalDate())) {
                groubleStartTime = globalSettingsForScheduling.getSchedulingStartTime().atStartOfDay();
            }
        }
        return groubleStartTime;
    }

    /**
     * 工厂设备排期的真实情况举例：条件：设备上班工作时段："00:00:00-08:00:00;08:30:00-16:00:00;16:30:00-23:30:00"，设备的可使用时间起点为：start:"2023-06-01 07:25:00"
     * 1.同一天，在一个时间区间内的情况：设备持续工作workTime:1800000ms，则输出设备的工作时间段为开始时间和结束时间为："2023-06-01 07:25:00"～"2023-06-01 07:55:00"
     * 2.同一天跨一个时间区间的情况：设备持续工作workTime:3600000ms，则输出设备的工作时间段为开始时间和结束时间为："2023-06-01 07:25:00"～"2023-06-01 08:00:00","2023-06-01 08:30:00"~"2023-06-01 08:55:00"
     * 3.同一天跨两个或者多个时间区间的情况：设备持续工作workTime:3600000*9ms，则输出设备的工作时间段为开始时间和结束时间为："2023-06-01 07:25:00"~"2023-06-01 08:00:00","2023-06-01 08:30:00"~"2023-06-01 16:00:00","2023-06-01 16:30:00"~"2023-06-01 17:25:00"
     * 4.跨天情况的情况：设备持续工作workTime:3600000*16ms，则输出设备的工作时间段为开始时间和结束时间为："2023-06-01 07:25:00"-"2023-06-01 08:00:00","2023-06-01 08:30:00"~"2023-06-01 16:00:00","2023-06-01 16:30:00"~"2023-06-01 23:30:00","2023-06-02 000:00:00"~"2023-06-02 01:05:00"
     */
    /**
     * 计算排程时段
     *
     * @param workTime         工作时间
     * @param workingPeriodMap 设备工作日历
     * @param workDays         工作天数
     * @param schedule         时段
     * @param start            排程开始时间
     */
    private static void getSchedule(long workTime, Map<LocalDate, DeviceWorkingCalendar> workingPeriodMap, List<LocalDate> workDays, List<String> schedule, LocalDateTime start) {
        int index = 0;
        while (workTime > 0) {
            String[] timeRanges = workingPeriodMap.get(start.toLocalDate()).getStartAndEndTimeIntervalSet().split(";");
            String[] timeRange = timeRanges[index % timeRanges.length].split(StrUtil.DASHED);
            LocalDateTime startTime = start;
            LocalDate startDay;
            if (startTime.toLocalTime().isBefore(LocalTime.parse(timeRange[0]))) {
                start = LocalTime.parse(timeRange[0]).atDate(startTime.toLocalDate());
                startTime = LocalTime.parse(timeRange[0]).atDate(startTime.toLocalDate());
            }
            LocalDateTime end = LocalTime.parse(timeRange[1]).atDate(startTime.toLocalDate());
            long duration = Duration.between(start, end).toMillis();
            if (duration < 0) {
                index++;
                if (index % timeRanges.length != 0) {
                    continue;
                }
                index = 0;
                int workDaysIndex = workDays.indexOf(start.toLocalDate());
                timeRanges = workingPeriodMap.get(workDays.get(workDaysIndex + 1)).getStartAndEndTimeIntervalSet().split(";");
                timeRange = timeRanges[0].split(StrUtil.DASHED);
                start = LocalTime.parse(timeRange[0]).atDate(workDays.get(workDaysIndex + 1));
                startTime = LocalTime.parse(timeRange[0]).atDate(workDays.get(workDaysIndex + 1));
                end = LocalTime.parse(timeRange[1]).atDate(startTime.toLocalDate());
                duration = Duration.between(start, end).toMillis();
            }
            if (duration > workTime) {
                end = LocalDateTime.ofInstant(Instant.ofEpochMilli(start.toInstant(ZoneOffset.of("+8")).toEpochMilli() + workTime), ZoneOffset.of("+8"));
                duration = workTime;
            }
            schedule.add(startTime.format(DatePattern.NORM_DATETIME_MINUTE_FORMATTER) + CommonConstants.WAVY_LINE + end.format(DatePattern.NORM_DATETIME_MINUTE_FORMATTER));
            workTime -= duration;
            if (workTime == 0) {
                break;
            }
            index++;
            timeRange = timeRanges[index % timeRanges.length].split(StrUtil.DASHED);
            if (index % timeRanges.length == 0) {
                int workDaysIndex = workDays.indexOf(start.toLocalDate());
                if (workDaysIndex == workDays.size() - 1) {
                    schedule.clear();
                    break;
                }
                startDay = workDays.get(workDaysIndex + 1);
                timeRanges = workingPeriodMap.get(startDay).getStartAndEndTimeIntervalSet().split(";");
                timeRange = timeRanges[0].split(StrUtil.DASHED);
                start = LocalTime.parse(timeRange[0]).atDate(startDay);
                index = 0;
            }
            startTime = start;
            end = LocalTime.parse(timeRange[0]).atDate(startTime.toLocalDate());
            start = end;
        }
    }

    /**
     * 获取设备工作日历
     *
     * @param deviceId  设备id
     * @param startDate 工作开始时间
     * @return 工作日历
     */
    private List<DeviceWorkingCalendar> getDeviceWorkingaCalendar(Long deviceId, LocalDate startDate) {
        return workingCalendarMapper.getDeviceWorkingaCalendar(deviceId, startDate);
    }

    /**
     * 获取设备工序信息
     *
     * @param technologicalProcess 工序工艺信息
     * @return 设备工序信息
     */
    private EquipmentProcess getEquipmentProcess(TechnologicalProcess technologicalProcess,Integer plannedQuantity) {
        Set<Long> equipmentids = equipmentMapper.getEquipments(technologicalProcess.getId(), technologicalProcess.getProcessId())
                .stream().map(Equipment::getId).collect(Collectors.toSet());
        if (equipmentids.isEmpty()) {
            throw new GlobalException("没有找到可用的设备");
        }
        List<ProcessDetails> processDetails = processDetailsService.query()
                .select("equipment_id", "max(planned_end_time) as planned_end_time")
                .in("equipment_id", equipmentids)
                .groupBy("equipment_id").list();
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime latestTime = LocalDateTime.now();
        Map<Long, LocalDateTime> processDetailsMap = processDetails.stream().collect(Collectors.toMap(ProcessDetails::getEquipmentId, ProcessDetails::getPlannedEndTime));
        List<ProcessEquipment> processEquipments = processEquipmentService.list(new LambdaQueryWrapper<ProcessEquipment>().eq(ProcessEquipment::getProcessProcessId, technologicalProcess.getId()).eq(ProcessEquipment::getProcessId, technologicalProcess.getProcessId()).in(ProcessEquipment::getEquipmentId, equipmentids));
        ProcessEquipment processEquipment = processEquipments.get(0);
        if(processDetailsMap.get(processEquipment.getEquipmentId())!=null&& processDetailsMap.get(processEquipment.getEquipmentId()).isAfter(latestTime)){
            latestTime = processDetailsMap.get(processEquipment.getEquipmentId());
        }
        BigDecimal spt = new BigDecimal(processEquipment.getProductionBeat()).divide(new BigDecimal(processEquipment.getProcessingBatch()), 2, RoundingMode.UP);
        List<ProcessEquipment> processEquipmentsEqu = new ArrayList<>();
        processEquipmentsEqu.add(processEquipment);
        for(int i=1;i<processEquipments.size();i++){
            ProcessEquipment p = processEquipments.get(i);
            LocalDateTime processEquipmentLatestTime = now;
            LocalDateTime pLatestTime = now;
            if(!processDetailsMap.isEmpty()){
                processEquipmentLatestTime = processDetailsMap.get(processEquipment.getEquipmentId());
                if(processEquipmentLatestTime==null|| processEquipmentLatestTime.isBefore(now)){
                    processEquipmentLatestTime = now;
                }
                pLatestTime = processDetailsMap.get(p.getEquipmentId());
                if(pLatestTime==null|| pLatestTime.isBefore(now)){
                    pLatestTime = now;
                }
            }
            latestTime = processEquipmentLatestTime;
            BigDecimal productionBeat = new BigDecimal(p.getProductionBeat()).divide(new BigDecimal(p.getProcessingBatch()), 2, RoundingMode.UP);
            if (processEquipmentLatestTime.plusSeconds(spt.multiply(new BigDecimal(plannedQuantity)).longValue()).isEqual(pLatestTime.plusSeconds(productionBeat.multiply(new BigDecimal(plannedQuantity)).longValue()))) {
                processEquipmentsEqu.add(p);
            }
            if (processEquipmentLatestTime.plusSeconds(spt.multiply(new BigDecimal(plannedQuantity)).longValue()).isAfter(pLatestTime.plusSeconds(productionBeat.multiply(new BigDecimal(plannedQuantity)).longValue()))) {
                spt = productionBeat;
                processEquipment = p;
                latestTime = pLatestTime;
                processEquipmentsEqu.clear();
                processEquipmentsEqu.add(p);
            }
        }
        if(processEquipmentsEqu.size()>1){
            Set<Integer> auxiliaryDataIds = processEquipmentsEqu.stream().map(ProcessEquipment::getAuxiliaryDataId).collect(Collectors.toSet());
            if(!auxiliaryDataIds.isEmpty()){
                List<ProcessDetails> detailsList = processDetailsService.query()
                        .select("auxiliary_data_id", "max(auxiliary_end_time) as auxiliary_end_time")
                        .in("auxiliary_data_id", auxiliaryDataIds)
                        .groupBy("auxiliary_data_id").list();
                if(!detailsList.isEmpty()){
                    Optional<ProcessDetails> processDetail = detailsList.stream().min(Comparator.comparing(ProcessDetails::getAuxiliaryEndTime));
                    auxiliaryDataIds.remove(processDetail.get().getAuxiliaryDataId());
                    if (auxiliaryDataIds.isEmpty()&&latestTime.isBefore(processDetail.get().getAuxiliaryEndTime())) {
                        latestTime = processDetail.get().getAuxiliaryEndTime();
                        processEquipmentsEqu = processEquipmentsEqu.stream().filter(p -> p.getAuxiliaryDataId().equals(processDetail.get().getAuxiliaryDataId())).collect(Collectors.toList());
                        processEquipment = processEquipmentsEqu.get(0);
                    }
                    if(!auxiliaryDataIds.isEmpty()){
                        processEquipmentsEqu = processEquipmentsEqu.stream().filter(p -> auxiliaryDataIds.contains(p.getAuxiliaryDataId())).collect(Collectors.toList());
                        processEquipment = processEquipmentsEqu.get(0);
                    }
                }
            }
        }
        if (processEquipmentsEqu.size()==1&&processEquipment.getAuxiliaryDataId() != null) {
            ProcessDetails processDetail = processDetailsService.query()
                    .select("auxiliary_data_id", "max(auxiliary_end_time) as auxiliary_end_time")
                    .eq("auxiliary_data_id", processEquipment.getAuxiliaryDataId())
                    .one();
            if (processDetail!=null&&latestTime.isBefore(processDetail.getAuxiliaryEndTime())) {
                latestTime = processDetail.getAuxiliaryEndTime();
            }
        }
        if (latestTime.isBefore(LocalDateTime.now())) {
            latestTime = LocalDateTime.now();
        }
        return EquipmentProcess.builder().
                id(processEquipment.getId()).equipmentId(processEquipment.getEquipmentId()).
                processId(processEquipment.getProcessId()).
                processName(technologicalProcess.getProcessName()).
                processRouteId(processEquipment.getProcessRouteId()).
                processNumber(technologicalProcess.getProcessNumber()).
                processCode(technologicalProcess.getProcessCode()).
                auxiliaryDataId(processEquipment.getAuxiliaryDataId()).
                productionBeat(processEquipment.getProductionBeat()).
                processingBatch(processEquipment.getProcessingBatch()).
                processProcessId(processEquipment.getProcessProcessId()).
                materialId(processEquipment.getMaterialId()).
                replacementTime(technologicalProcess.getReplacementTime()).
                transshipmentBatch(technologicalProcess.getTransshipmentBatch()).
                transshipmentTime(technologicalProcess.getTransshipmentTime()).
                processingContent(technologicalProcess.getProcessingContent())
                .latestTime(latestTime).build();
    }

    /**
     * 计算排程时间段
     *
     * @param suggestedPlan 建议计划
     * @param deviceId      设备id
     * @param startTime     开始时间
     * @param workTime      工作时间
     * @return 排程时间段
     */
    private List<String> calculateTimePeriods(SuggestedPlan suggestedPlan, Long deviceId, LocalDateTime startTime, long workTime) {
        if (workTime < 60000) {
            throw new GlobalException(String.format("订单【订单号%s】下的物料【%s｜%s】生产时间小于一分钟，请确认对应工艺流程是否配置正确！",
                    suggestedPlan.getOrderNumber(), suggestedPlan.getMaterialName(), suggestedPlan.getMaterialCode()));
        }
        List<DeviceWorkingCalendar> deviceWorkingaCalendar = getDeviceWorkingaCalendar(deviceId, startTime.toLocalDate());
        Equipment equipment = equipmentService.getById(deviceId);
        if (CollectionUtils.isEmpty(deviceWorkingaCalendar)) {
            String message = String.format("订单【订单号%s】下的物料【%s｜%s】，加工设备【%s｜%s】未绑定工作日历或绑定的工作日历不足以排产完对应的生产任务，请在工作日历管理中进行配置！",
                    suggestedPlan.getOrderNumber(), suggestedPlan.getMaterialName(), suggestedPlan.getMaterialCode(),
                    equipment.getDeviceName(), equipment.getEquipmentNumber());
            throw new GlobalException(message);
        }
        List<String> calculateTimePeriods = calculateTimePeriods(deviceWorkingaCalendar, startTime, workTime);
        if (calculateTimePeriods.isEmpty()) {
            throw new GlobalException(String.format("订单【订单号%s】下的物料【%s｜%s】，加工设备【%s｜%s】绑定工作日历不足以排产完对应的生产任务，请核对工作日历！",
                    suggestedPlan.getOrderNumber(), suggestedPlan.getMaterialName(), suggestedPlan.getMaterialCode(),
                    equipment.getDeviceName(), equipment.getEquipmentNumber()));
        }
        return calculateTimePeriods;
    }

    /**
     * 获取设备工艺信息
     *
     * @param materialCode 物料编码
     * @return 设备工艺信息
     */
    private List<EquipmentProcess> getProcessEquipments(String materialCode,Integer plannedQuantity) {
        List<EquipmentProcess> equipmentProcesses;
        try {
            List<TechnologicalProcess> technologicalProcesses = getTechnologicalProcesses(materialCode);
            equipmentProcesses = technologicalProcesses.stream().map(t-> getEquipmentProcess(t, plannedQuantity)).sorted(Comparator.comparingInt(EquipmentProcess::getProcessNumber)).collect(Collectors.toList());
        } catch (Exception e) {
            throw new GlobalException(String.format("物料：%s没有对应的工序", materialCode));
        }
        return equipmentProcesses;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void planingOperation(PlanOperationProgress planOperationProgress) throws Exception {
        List<SuggestedPlan> suggestedPlans = getSuggestedPlans();
        if (CollectionUtil.isEmpty(suggestedPlans)) {
            updatePlanOperationProgress(planOperationProgress);
            return;
        }
        Map<String, List<SuggestedPlan>> suggestedPlansMap = getSuggestedPlansMap(suggestedPlans);
        updateSchedulingStatus(suggestedPlans, SchedulingStatusEnum.ARRANGING);
        List<SuggestedPlan> parentSuggestPlans = suggestedPlans.stream().filter(s -> s.getLevel() == 0).collect(Collectors.toList());
        if (CollectionUtil.isEmpty(parentSuggestPlans)) {
            throw new GlobalException("没有查询到需要排程的订单！");
        }
        GlobalSettingsForScheduling globalSettingsForScheduling = globalSettingsForSchedulingService.getOne(new LambdaQueryWrapper<GlobalSettingsForScheduling>());
        LocalDateTime groubleStartTime = getGroubleStartTime(globalSettingsForScheduling);
        List<LocalDateTime> groubleEndTime = new ArrayList<>();
        updateOrderInformations(parentSuggestPlans);
        if (planingOperation(parentSuggestPlans, suggestedPlansMap, groubleStartTime, groubleEndTime, planOperationProgress)) {
            throw new GlobalException("建议计划运算失败");
        }
        updateSchedulingStatus(suggestedPlans, SchedulingStatusEnum.ARRANGED);
        updatePlanOperationProgress(planOperationProgress);
    }

    /**
     * 更新排程进度
     *
     * @param planOperationProgress 排程进度model
     */
    private void updatePlanOperationProgress(PlanOperationProgress planOperationProgress) {
        if (planOperationProgress == null) {
            return;
        }
        planOperationProgress.setStatus(CommonConstants.SUCCESS);
        planOperationProgress.setRate(new BigDecimal(100));
        String key = RedisUtil.key(RedisConstants.PLANING_OPERATION, planOperationProgress.getId());
        RedisUtil.setCacheMap(key, planOperationProgress, Duration.ofSeconds(RedisConstants.PLANING_EXPIRE_TIME));
    }

    /**
     * 更新排程进度
     *
     * @param planOperationProgress 排程进度model
     * @param i                     订单下标
     * @param size                  订单数
     */
    private void updatePlanOperationProgress(PlanOperationProgress planOperationProgress, int i, int size) {
        if (planOperationProgress == null) {
            return;
        }
        BigDecimal rate = new BigDecimal(i).divide(new BigDecimal(size), 2, RoundingMode.UP).multiply(new BigDecimal(80)).add(new BigDecimal(20));
        planOperationProgress.setRate(rate);
        String key = RedisUtil.key(RedisConstants.PLANING_OPERATION, planOperationProgress.getId());
        RedisUtil.setCacheMap(key, planOperationProgress, Duration.ofSeconds(RedisConstants.PLANING_EXPIRE_TIME));
    }

    /**
     * 建议计划-计划排程
     *
     * @param parentSuggestPlans    父建议计划
     * @param suggestedPlansMap     建议计划
     * @param groubleStartTime      全局计划排程开始时间
     * @param groubleEndTime        全局计划排程结束时间
     * @param planOperationProgress 排程进度model
     * @return true，false
     */
    private boolean planingOperation(List<SuggestedPlan> parentSuggestPlans, Map<String, List<SuggestedPlan>> suggestedPlansMap, LocalDateTime groubleStartTime, List<LocalDateTime> groubleEndTime, PlanOperationProgress planOperationProgress) {
        int size = parentSuggestPlans.size();
        List<OrderInformation> orderInformations = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            SuggestedPlan suggestedPlan = parentSuggestPlans.get(i);
            List<SuggestedPlan> allSuggestedPlans = new ArrayList<>();
            List<SuggestedPlan> childrenSuggestedPlans = suggestedPlansMap.get(suggestedPlan.getOrderNumber());
            List<SuggestedPlan> selfProducts = childrenSuggestedPlans.stream().filter(s -> s.getProductionType().equals(ProductionType.SELF_MADE)).toList();
            List<SuggestedPlan> noSelfProducts = childrenSuggestedPlans.stream().filter(s -> !s.getProductionType().equals(ProductionType.SELF_MADE)).toList();
            SortedMap<Integer, List<SuggestedPlan>> selfProductsMap = selfProducts.stream().collect(Collectors.groupingBy(SuggestedPlan::getLevel, TreeMap::new, Collectors.toList())).descendingMap();
            SortedMap<Integer, List<SuggestedPlan>> noSelfProductsMap = noSelfProducts.stream().collect(Collectors.groupingBy(SuggestedPlan::getLevel, TreeMap::new, Collectors.toList())).descendingMap();
            Map<String, List<SuggestedPlan>> selfAllProductsMap = selfProducts.stream().filter(s -> !s.getMaterialCode().equals(s.getMaterialParentCode())).collect(Collectors.groupingBy(s -> String.join(",", s.getOrderNumber(), s.getMaterialParentCode())));
            if (selfProductsMap.isEmpty() && noSelfProductsMap.isEmpty()) {
                return true;
            }
            //计算自制件
            getScheduleTime(groubleStartTime, selfAllProductsMap, selfProductsMap, allSuggestedPlans);
            //计算非自制件
            if (CollectionUtil.isNotEmpty(noSelfProductsMap)) {
                updateNoSelfProductsMap(allSuggestedPlans, noSelfProductsMap);
            }
            LocalDateTime parentStartTime = allSuggestedPlans.stream().map(SuggestedPlan::getPlanStartTime).min(LocalDateTime::compareTo).get();
            LocalDateTime parentEndTime = allSuggestedPlans.stream().map(SuggestedPlan::getPlanCompletionTime).max(LocalDateTime::compareTo).get();
            groubleEndTime.add(parentEndTime);
            //更新需求订单的时间
            updateOrderInformation(orderInformations, suggestedPlan, parentStartTime, parentEndTime);
            updatePlanOperationProgress(planOperationProgress, i + 1, size);
        }
        if (CollectionUtil.isNotEmpty(orderInformations)) {
            orderInformationService.updateBatchById(orderInformations);
        }
        return false;
    }

    /**
     * 计算自制时间
     *
     * @param groubleStartTime   全局排程开始时间
     * @param selfAllProductsMap 全部自制件map
     * @param selfProductsMap    自制件map
     * @param allSuggestedPlans  全部建议计划map
     */
    private void getScheduleTime(LocalDateTime groubleStartTime, Map<String, List<SuggestedPlan>> selfAllProductsMap, SortedMap<Integer, List<SuggestedPlan>> selfProductsMap, List<SuggestedPlan> allSuggestedPlans) {
        List<SuggestedPlan> suggestedPlans = new ArrayList<>();
        List<SchedulingTaskView> scheduledTaskViews = new ArrayList<>();
        List<ProcessDetails> processDetailss = new ArrayList<>();
        selfProductsMap.forEach((k, v) -> v.forEach(sp -> {
            List<EquipmentProcess> equipmentProcesses = getProcessEquipments(sp.getMaterialCode(),
                    sp.getPlannedQuantity() == null ? 0 : sp.getPlannedQuantity().intValue());
            equipmentProcesses.sort(Comparator.comparing(EquipmentProcess::getProcessNumber));
            long workTime;
            LocalDateTime scheduleTime = groubleStartTime;
            List<SuggestedPlan> suggestedPlanList = selfAllProductsMap.get(String.join(",", sp.getOrderNumber(), sp.getMaterialCode()));
            if (CollectionUtil.isNotEmpty(suggestedPlanList)) {
                Optional<LocalDateTime> planCompletionTime = suggestedPlanList.stream().map(SuggestedPlan::getPlanCompletionTime).max(LocalDateTime::compareTo);
                if (planCompletionTime.isPresent()) {
                    scheduleTime = planCompletionTime.get();
                }
            }
            List<ProcessDetails> subProcessDetailss = new ArrayList<>();
            //计算工艺工序
            for (EquipmentProcess pe : equipmentProcesses) {
                Long equipmentId = pe.getEquipmentId();
                workTime = getWorkTime(pe, sp.getPlannedQuantity() == null ? 0 : sp.getPlannedQuantity().intValue());
                LocalDateTime latestTime = pe.getLatestTime().isBefore(scheduleTime) ? scheduleTime : pe.getLatestTime();
                latestTime = getLatestTime(pe, processDetailss, equipmentId, latestTime);
                List<String> timePeriods = calculateTimePeriods(sp, equipmentId, latestTime, workTime);
                String endTime = timePeriods.get(timePeriods.size() - 1).split(CommonConstants.WAVY_LINE)[1];
                scheduleTime = LocalDateTime.parse(endTime, DatePattern.NORM_DATETIME_MINUTE_FORMATTER);
                //插入工序明细，插入设备计划表，更新设备的最新工作时间
                Equipment equipment = saveProcessDetails(subProcessDetailss, processDetailss, sp, pe, timePeriods);
                saveEquipmentLatestTime(equipment, LocalDateTime.parse(timePeriods.get(timePeriods.size() - 1).split(CommonConstants.WAVY_LINE)[1], DatePattern.NORM_DATETIME_MINUTE_FORMATTER));
            }
            //插入子集排产视图
            LocalDateTime planStartTime = subProcessDetailss.stream().map(ProcessDetails::getPlannedStartTime).min(LocalDateTime::compareTo).get();
            LocalDateTime planEndTime = subProcessDetailss.stream().map(ProcessDetails::getPlannedEndTime).max(LocalDateTime::compareTo).get();
            addSchedulingTaskView(scheduledTaskViews, sp, planStartTime, planEndTime);
            // 更新子集建议计划相关的时间
            addSuggestedPlan(suggestedPlans, sp, planStartTime, planEndTime);
            allSuggestedPlans.add(sp);
        }));
    }

    private LocalDateTime getLatestTime(EquipmentProcess pe, List<ProcessDetails> processDetailss, Long equipmentId, LocalDateTime latestTime) {
        List<ProcessDetails> processDetailsList = processDetailss.stream().filter(p -> p.getEquipmentId().equals(equipmentId)).toList();
        if(!CollectionUtils.isEmpty(processDetailsList)){
            LocalDateTime planEndTime = processDetailsList.stream().map(ProcessDetails::getPlannedEndTime).max(LocalDateTime::compareTo).get();
            latestTime = latestTime.isBefore(planEndTime) ? planEndTime : latestTime;
        }
        if(pe.getAuxiliaryDataId()!=null){
            processDetailsList = processDetailss.stream().filter(p -> pe.getAuxiliaryDataId().equals(p.getAuxiliaryDataId())).toList();
            if(!CollectionUtils.isEmpty(processDetailsList)){
                LocalDateTime planEndTime = processDetailsList.stream().map(ProcessDetails::getAuxiliaryEndTime).max(LocalDateTime::compareTo).get();
                latestTime = latestTime.isBefore(planEndTime) ? planEndTime : latestTime;
            }
        }
        return latestTime;
    }

    /**
     * 更新设备最后工作时间
     *
     * @param equipment   设备
     * @param lastestTime 设备最晚工作时间
     */
    private void saveEquipmentLatestTime(Equipment equipment, LocalDateTime lastestTime) {
        equipment.setLatestTime(lastestTime);
        equipmentService.updateById(equipment);
    }

    /**
     * 增加任务视图
     *
     * @param scheduledTaskViews 任务视图
     * @param sp                 建议计划
     * @param equipmentStartTime 计划开始时间
     * @param equipmentEndTime   计划结束时间
     */
    private void addSchedulingTaskView(List<SchedulingTaskView> scheduledTaskViews, SuggestedPlan sp, LocalDateTime equipmentStartTime, LocalDateTime equipmentEndTime) {
        SchedulingTaskView schedulingTaskView;
        schedulingTaskView = SchedulingTaskView.builder()
                .orderId(sp.getOrderId())
                .orderNumber(sp.getOrderNumber()).materialId(sp.getMaterialId()).itemNumber(sp.getMaterialCode())
                .itemName(sp.getMaterialName()).demandQuantity(sp.getDemandQuantity())
                .plannedQuantity(sp.getPlannedQuantity())
                .orderDeliveryTime(sp.getOrderDeliveryTime())
                .orderStatus(SchedulingStatusEnum.ARRANGED)
                .scheduledQuantity(sp.getPlannedQuantity())
                .unscheduledQuantity(new BigDecimal(0))
                .plannedStartTime(equipmentStartTime)
                .plannedEndTime(equipmentEndTime)
                .build();
        schedulingTaskViewService.save(schedulingTaskView);
    }

    /**
     * 增加建议计划
     *
     * @param suggestedPlans     建议计划list
     * @param sp                 建议计划
     * @param equipmentStartTime 计划开始时间
     * @param equipmentEndTime   计划结束时间
     */
    private void addSuggestedPlan(List<SuggestedPlan> suggestedPlans, SuggestedPlan sp, LocalDateTime equipmentStartTime, LocalDateTime equipmentEndTime) {
        sp.setDemandTime(equipmentEndTime.toLocalDate());
        sp.setPlanStartTime(equipmentStartTime);
        sp.setPlanCompletionTime(equipmentEndTime);
        sp.setSchedulingStatus(SchedulingStatusEnum.ARRANGED);
        suggestedPlanService.updateById(sp);
    }

    /**
     * 更新自制件信息
     *
     * @param allSuggestedPlans 全部建议计划
     * @param noSelfProductsMap 非自制件
     */
    private void updateNoSelfProductsMap(List<SuggestedPlan> allSuggestedPlans, SortedMap<Integer, List<SuggestedPlan>> noSelfProductsMap) {
        Map<String, SuggestedPlan> allSuggestedPlansMap = allSuggestedPlans.stream().collect(Collectors.toMap(SuggestedPlan::getMaterialCode, sp -> sp, (k1, k2) -> k2));
        List<SuggestedPlan> suggestedPlans = new ArrayList<>();
        List<PurchasePlan> purchasePlanes = new ArrayList<>();
        noSelfProductsMap.forEach((k, v) -> v.forEach(sp -> {
            String materialParentCode = sp.getMaterialParentCode();
            SuggestedPlan sgp = allSuggestedPlansMap.get(materialParentCode);
            if (sgp != null) {
                LocalDateTime planStartTime = sgp.getPlanStartTime();
                String materialCode = sp.getMaterialCode();
                Material material = materialService.getOne(new LambdaQueryWrapper<Material>().eq(Material::getMaterialCode, materialCode));
                Long time = getTime(sp, material);

                planStartTime = planStartTime.plusDays(-1);
                sp.setDemandTime(planStartTime.toLocalDate());
                if (sp.getImmediateInventory().compareTo(sp.getPlannedQuantity()) < 0) {
                    sp.setPlanStartTime(planStartTime.plusDays(-time));
                    sp.setPlanCompletionTime(planStartTime);
                }
                sp.setSchedulingStatus(SchedulingStatusEnum.ARRANGED);
                //更新建议计划外购，外协
                suggestedPlans.add(sp);
                addPurchasePlan(purchasePlanes, sp, material, planStartTime.toLocalDate(), time);
            }
        }));
        if (CollectionUtil.isNotEmpty(suggestedPlans)) {
            suggestedPlanService.updateBatchById(suggestedPlans);
        }
        if (CollectionUtil.isNotEmpty(purchasePlanes)) {
            purchasePlanService.updateBatchById(purchasePlanes);
        }
    }

    /**
     * 获取采购天数
     *
     * @param sp       建议计划
     * @param material 物料信息
     * @return 采购天数
     */
    private Long getTime(SuggestedPlan sp, Material material) {
        Long fixedLeadTimeBatch = material.getFixedLeadTimeBatch();
        Long fixedLeadTime = material.getFixedLeadTime();
        Integer plannedQuantity = sp.getPlannedQuantity() == null ? 0 : sp.getPlannedQuantity().intValue();
        Long time = (plannedQuantity * fixedLeadTime) / fixedLeadTimeBatch;
        if ((plannedQuantity * fixedLeadTime) % fixedLeadTimeBatch != 0) {
            time = time + 1;
        }
        return time;
    }

    /**
     * 增加采购计划
     *
     * @param purchasePlanes 采购计划
     * @param sp             建议计划
     * @param material       物料信息
     * @param planStartTime  计划开始时间
     * @param time           采购天数
     */
    private void addPurchasePlan(List<PurchasePlan> purchasePlanes, SuggestedPlan sp, Material material, LocalDate planStartTime, Long time) {
        PurchasePlan purchasePlan = purchasePlanService.getOne(new LambdaQueryWrapper<PurchasePlan>().eq(PurchasePlan::getOrderNumber, sp.getOrderNumber()).eq(PurchasePlan::getItemCode, material.getMaterialCode()));
        if (purchasePlan == null) {
            return;
        }
        purchasePlan.setProposedPurchaseDate(planStartTime.plusDays(-time));
        purchasePlan.setDemandPeriod(planStartTime);
        purchasePlan.setProcurementLeadTime(planStartTime);
        MaterialSupplier materialSupplier = supplierMapper.getMaterialSupplier(material.getMaterialCode());
        if (materialSupplier != null) {
            purchasePlan.setSupplierId(materialSupplier.getId());
            purchasePlan.setSupplierCode(materialSupplier.getSupplierCode());
            purchasePlan.setSupplierName(materialSupplier.getSupplierName());
        }
        purchasePlanes.add(purchasePlan);
    }

    /**
     * 更新订单信息
     *
     * @param orderInformations 订单信息
     * @param suggestedPlan     建议计划
     * @param parentEndTime     计划结束时间
     */
    private void updateOrderInformation(List<OrderInformation> orderInformations, SuggestedPlan suggestedPlan, LocalDateTime parentStartTime, LocalDateTime parentEndTime) {
        OrderInformation orderInformation = orderInformationService.getOne(new LambdaQueryWrapper<OrderInformation>().eq(OrderInformation::getOrderNumber, suggestedPlan.getOrderNumber()).eq(OrderInformation::getItemCode, suggestedPlan.getMaterialCode()));
        orderInformation.setPlannedStartTime(parentStartTime);
        orderInformation.setPlannedEndTime(parentEndTime);
        orderInformation.setSchedulingStatus(SchedulingStatusEnum.ARRANGED);
        //处理计划延迟标签
        Set<LabelStatus> labelStatuses = orderInformation.removeLabelsValue(LabelStatus.PLAN_LATE);
        orderInformation.setPlanExtension((int) Duration.between(orderInformation.getOrderDeliveryDate().atTime(LocalTime.MIN), parentEndTime.with(LocalTime.MIN)).toDays());
        if (!parentEndTime.toLocalDate().isBefore(orderInformation.getOrderDeliveryDate())) {
            labelStatuses.add(LabelStatus.PLAN_LATE);
            orderInformation.setLabels(labelStatuses);
        }
        orderInformations.add(orderInformation);
    }

    /**
     * 设置工序排程明细
     *
     * @param subProcessDetailss 子级工序排程明细
     * @param processDetailss    工序排程明细
     * @param sp                 建议计划
     * @param pe                 设备工序
     * @param timePeriods        排程时段
     * @return 设备信息
     */
    private Equipment saveProcessDetails(List<ProcessDetails> subProcessDetailss, List<ProcessDetails> processDetailss, SuggestedPlan sp, EquipmentProcess pe, List<String> timePeriods) {
        Material material = materialService.getById(pe.getMaterialId());
        Equipment equipment = equipmentService.getById(pe.getEquipmentId());
        AuxiliaryData auxiliaryData = auxiliaryDataService.getById(pe.getAuxiliaryDataId());
        ProcessDetails processDetails = ProcessDetails.builder()
                .orderId(sp.getOrderId())
                .orderNumber(sp.getOrderNumber()).processId(pe.getProcessId())
                .processNumber(pe.getProcessNumber()).processName(pe.getProcessName())
                .processCode(pe.getProcessCode())
                .processDescription(pe.getProcessingContent()).materialId(pe.getMaterialId())
                .materialCode(material.getMaterialCode()).materialName(material.getMaterialName())
                .equipmentId(pe.getEquipmentId()).equipmentCode(equipment.getEquipmentNumber())
                .equipmentName(equipment.getDeviceName()).workshopId(equipment.getWorkshopId())
                .workshopCode(equipment.getWorkshopCode()).workshopName(equipment.getWorkshopName())
                .demandQuantity(sp.getDemandQuantity()).plannedQuantity(sp.getPlannedQuantity())
                .plannedStartTime(LocalDateTime.parse(timePeriods.get(0).split(CommonConstants.WAVY_LINE)[0], DatePattern.NORM_DATETIME_MINUTE_FORMATTER))
                .plannedEndTime(LocalDateTime.parse(timePeriods.get(timePeriods.size() - 1).split(CommonConstants.WAVY_LINE)[1], DatePattern.NORM_DATETIME_MINUTE_FORMATTER))
                .specificTime(timePeriods.toString()).build();
        if(auxiliaryData!=null){
            processDetails.setAuxiliaryDataId(auxiliaryData.getId());
            processDetails.setAuxiliaryDataCoding(auxiliaryData.getAuxiliaryResourceCoding());
            processDetails.setAuxiliaryDataName(auxiliaryData.getAuxiliaryResourceName());
            processDetails.setAuxiliaryBeginTime(LocalDateTime.parse(timePeriods.get(0).split(CommonConstants.WAVY_LINE)[0], DatePattern.NORM_DATETIME_MINUTE_FORMATTER));
            processDetails.setAuxiliaryEndTime(LocalDateTime.parse(timePeriods.get(timePeriods.size() - 1).split(CommonConstants.WAVY_LINE)[1], DatePattern.NORM_DATETIME_MINUTE_FORMATTER));
        }
        subProcessDetailss.add(processDetails);
        processDetailss.add(processDetails);
        processDetailsService.save(processDetails);
        return equipment;
    }

    /**
     * 更新订单信息
     *
     * @param parentSuggestPlans 父级建议计划
     */
    private void updateOrderInformations(List<SuggestedPlan> parentSuggestPlans) {
        List<String> orderNumbers = parentSuggestPlans.stream().map(SuggestedPlan::getOrderNumber).collect(Collectors.toList());
        List<OrderInformation> orderInformations = orderInformationService.list(new LambdaQueryWrapper<OrderInformation>().in(OrderInformation::getOrderNumber, orderNumbers));
        orderInformations.forEach(o -> o.setSchedulingStatus(SchedulingStatusEnum.ARRANGING));
        orderInformationService.updateBatchById(orderInformations);
    }

    /**
     * 更新任务视图
     *
     * @param suggestedPlans 建议计划
     * @param status         状态
     */
    private void updateSchedulingStatus(List<SuggestedPlan> suggestedPlans, SchedulingStatusEnum status) {
        suggestedPlans.forEach(s -> s.setSchedulingStatus(status));
        suggestedPlanService.updateBatchById(suggestedPlans);
    }

    /**
     * 获取建议计划
     *
     * @return 建议计划
     */
    private List<SuggestedPlan> getSuggestedPlans() {
        return suggestedPlanService.list(new LambdaQueryWrapper<SuggestedPlan>().eq(SuggestedPlan::getSchedulingStatus, SchedulingStatusEnum.NOTARRANGED.getCode()).orderByAsc(SuggestedPlan::getId));
    }

    /**
     * 获取建议计划map
     *
     * @param suggestedPlans 建议计划
     * @return 建议计划map
     */
    private Map<String, List<SuggestedPlan>> getSuggestedPlansMap(List<SuggestedPlan> suggestedPlans) {
        return suggestedPlans.stream().collect(Collectors.groupingBy(SuggestedPlan::getOrderNumber, TreeMap::new, Collectors.toList()));
    }

    /**
     * 查询工艺工序
     *
     * @param materialCode 物料编码
     * @return 工艺工序
     */
    private List<TechnologicalProcess> getTechnologicalProcesses(String materialCode) {
        return technologicalProcessMapper.selectList(new LambdaQueryWrapper<TechnologicalProcess>().eq(TechnologicalProcess::getMaterialCode, materialCode));
    }

    /**
     * 计算工作时间
     *
     * @param equipmentProcess 设备工艺
     * @param orderNum         订单编码
     * @return 工作时间
     */
    private long getWorkTime(EquipmentProcess equipmentProcess, int orderNum) {
        Long processingBatch = equipmentProcess.getProcessingBatch();
        Long productionBeat = equipmentProcess.getProductionBeat();
        Long replacementTime = equipmentProcess.getReplacementTime();
        Long transshipmentBatch = equipmentProcess.getTransshipmentBatch();
        Long transshipmentTime = equipmentProcess.getTransshipmentTime();
        long productionBeatTime = ((long) orderNum * productionBeat / processingBatch) * 1000;
        long transshipmentBatchTime = ((long) orderNum * transshipmentTime / transshipmentBatch) * 1000;
        return productionBeatTime + transshipmentBatchTime + replacementTime * 1000L;
    }

    /**
     * 计算工作时段
     *
     * @param workTimes 工作日历
     * @param startTime 计划开始时间
     * @param workTime  工作时间
     * @return 工作时段
     */
    private List<String> calculateTimePeriods(List<DeviceWorkingCalendar> workTimes, LocalDateTime startTime, long workTime) {
        Map<LocalDate, DeviceWorkingCalendar> workingPeriodMap = workTimes.stream().collect(Collectors.toMap(DeviceWorkingCalendar::getDate, workingPeriod -> workingPeriod));
        List<LocalDate> workDays = workTimes.stream().map(DeviceWorkingCalendar::getDate).collect(Collectors.toList());
        List<String> schedule = new ArrayList<>();
        LocalDate lastWorkDay = workTimes.get(workTimes.size() - 1).getDate();
        LocalDate firstWorkDay = workTimes.get(0).getDate();
        if (startTime.toLocalDate().isBefore(firstWorkDay)) {
            startTime = firstWorkDay.atStartOfDay();
        }
        if (startTime.toLocalDate().isAfter(lastWorkDay)) {
            return schedule;
        }
        if (startTime.toLocalDate().isEqual(lastWorkDay)) {
            String[] timeRanges = workingPeriodMap.get(startTime.toLocalDate()).getStartAndEndTimeIntervalSet().split(";");
            String[] timeRange = timeRanges[timeRanges.length - 1].split(StrUtil.DASHED);
            LocalDateTime end = LocalTime.parse(timeRange[1]).atDate(startTime.toLocalDate());
            long duration = Duration.between(startTime, end).toMillis();
            if (duration < workTime) {
                return schedule;
            }
        }
        getSchedule(workTime, workingPeriodMap, workDays, schedule, startTime);
        return schedule;
    }
}
